home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.1 (Developer) [x86] / NeXT Step 3.1 Intel dev.cdr.dmg / NextDeveloper / Examples / AppKit / Draw / Image.m < prev    next >
Text File  |  1993-01-06  |  17KB  |  654 lines

  1. #import "draw.h"
  2.  
  3. /* Optimally viewed in a wide window.  Make your window big enough so that this comment fits on one line without wrapping. */
  4.  
  5. /*
  6.  * Image is a simple graphic which takes PostScript or
  7.  * TIFF images and draws them in a bounding box (it scales
  8.  * the image if the bounding box is changed).  It is
  9.  * implemented using the NXImage class.  Using NXImage
  10.  * here is especially nice since it images its PostScript
  11.  * in a separate context (thus, any errors that PostScript
  12.  * generates will not affect our main drawing context).
  13.  */
  14.  
  15. @implementation Image : Graphic
  16.  
  17. /* Initialize the class */
  18.  
  19. + initialize
  20. {
  21.     [Image setVersion:7];
  22.     return self;
  23. }
  24.  
  25. /* Factory methods. */
  26.  
  27. + highlightedLinkButtonImage:(NXSize *)size
  28. /*
  29.  * Just makes an NXLinkButtonH NXImage the same size as
  30.  * the size passed in.  I suppose this could just be a
  31.  * function.
  32.  */
  33. {
  34.     static NXImage *retval = nil;
  35.     if (!retval) {
  36.     retval = [[NXImage findImageNamed:"NXLinkButtonH"] copy];
  37.     [retval setScalable:YES];
  38.     [retval setDataRetained:YES];
  39.     }
  40.     [retval setSize:size];
  41.     return retval;
  42. }
  43.  
  44. + (BOOL)canInitFromPasteboard:(Pasteboard *)pboard
  45. {
  46.     return [NXImage canInitFromPasteboard:pboard];
  47. }
  48.  
  49. static BOOL checkImage(NXImage *anImage)
  50. /*
  51.  * Locking focus on an NXImage forces it to draw and thus verifies
  52.  * whether there are any PostScript or TIFF errors in the source of
  53.  * the image.  lockFocus returns YES only if there are no errors.
  54.  */
  55. {
  56.     if ([anImage lockFocus]) {
  57.     [anImage unlockFocus];
  58.     return YES;
  59.     }
  60.     return NO;
  61. }
  62.  
  63. /* Creation/Initialization Methods */
  64.  
  65. - init
  66. /*
  67.  * This creates basically an "empty" Image.
  68.  * This is the designated initializer for Image.
  69.  * Be careful, however, because by the time this
  70.  * returns, a newly initialized Image may not be
  71.  * fully initialized (it'll be "valid," just not
  72.  * necessarily fully initialized).  If you want that
  73.  * behaviour, override finishedWithInit.
  74.  */
  75. {
  76.     [super init];
  77.     originalSize.width = originalSize.height = 1.0;
  78.     bounds.size = originalSize;
  79.     return self;
  80. }
  81.  
  82. - finishedWithInit
  83. /*
  84.  * Called when a newly initialized Image is fully
  85.  * initialized and ready to roll.  For subclassers
  86.  * only.
  87.  */
  88. {
  89.     return self;
  90. }
  91.  
  92. - initEmpty
  93. /*
  94.  * Creates a blank Image.
  95.  */
  96. {
  97.     [self init];
  98.     return [self finishedWithInit];
  99. }
  100.  
  101. - initFromStream:(NXStream *)stream
  102. /*
  103.  * Creates a new NXImage and sets it to be scalable and to retain
  104.  * its data (which means that when we archive it, it will actually
  105.  * write the TIFF or PostScript data into the stream).
  106.  */
  107. {
  108.     [self init];
  109.  
  110.     if (stream) {
  111.     image = [NXImage allocFromZone:[self zone]];
  112.     if ((image = [image initFromStream:stream])) {
  113.         [image setDataRetained:YES];
  114.         if (checkImage(image)) {
  115.         [image getSize:&originalSize];
  116.         [image setScalable:YES];
  117.         bounds.size = originalSize;
  118.         return [self finishedWithInit];
  119.         }
  120.     }
  121.     }
  122.  
  123.     [self free];
  124.  
  125.     return nil;
  126. }
  127.  
  128. - initFromPasteboard:(Pasteboard *)pboard;
  129. /*
  130.  * Creates a new NXImage and sets it to be scalable and to retain
  131.  * its data (which means that when we archive it, it will actually
  132.  * write the TIFF or PostScript data into the stream).
  133.  */
  134. {
  135.     [self init];
  136.  
  137.     if (pboard) {
  138.     image = [NXImage allocFromZone:[self zone]];
  139.     if ((image = [image initFromPasteboard:pboard])) {
  140.         [image setDataRetained:YES];
  141.         if (checkImage(image)) {
  142.         [image getSize:&originalSize];
  143.         [image setScalable:YES];
  144.         bounds.size = originalSize;
  145.         return [self finishedWithInit];
  146.         }
  147.     }
  148.     }
  149.  
  150.     [self free];
  151.  
  152.     return nil;
  153. }
  154.  
  155. - initFromFile:(const char *)file
  156. /*
  157.  * Creates an NXImage by reading data from an .eps or .tiff file.
  158.  */
  159. {
  160.     [self init];
  161.  
  162.     image = [[NXImage allocFromZone:[self zone]] init];
  163.     if ([image loadFromFile:file]) {
  164.     [image setDataRetained:YES];
  165.     if (checkImage(image)) {
  166.         [image getSize:&originalSize];
  167.         [image setScalable:YES];
  168.         bounds.size = originalSize;
  169.         return [self finishedWithInit];
  170.     }
  171.     }
  172.  
  173.     [self free];
  174.  
  175.     return nil;
  176. }
  177.  
  178. - doInitFromImage:(NXImage *)anImage
  179. /*
  180.  * Common code for initFromImage: and unarchiving.
  181.  */
  182. {
  183.     if (anImage) {
  184.     image = anImage;
  185.     [image getSize:&originalSize];
  186.     [image setScalable:YES];
  187.     [image setDataRetained:YES];
  188.     bounds.size = originalSize;
  189.     } else {
  190.     [self free];
  191.     self = nil;
  192.     }
  193.     return self;
  194. }
  195.  
  196. - initFromImage:(NXImage *)anImage
  197. /*
  198.  * Initializes an Image from a specific NXImage.
  199.  */
  200. {
  201.     [self init];
  202.     return [[self doInitFromImage:anImage] finishedWithInit];
  203. }
  204.  
  205. - initFromIcon:(NXImage *)anImage
  206. /*
  207.  * Same as initFromImage:, but we remember that this particular
  208.  * NXImage was actually a file icon (which enables us to double-click
  209.  * on it to open the icon, see handleEvent:).
  210.  */
  211. {
  212.     if ([self initFromImage:anImage]) {
  213.     amIcon = YES;
  214.     return self;
  215.     } else {
  216.     return nil;
  217.     }
  218. }
  219.  
  220. - initWithLinkButton
  221. /*
  222.  * Creates an image which is just the link button.
  223.  * This is only applicable with Object Links.
  224.  */
  225. {
  226.     if ([self initFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]]) {
  227.     amLinkButton = YES;
  228.     return self;
  229.     } else {
  230.     return nil;
  231.     }
  232. }
  233.  
  234. - (NXRect)resetImage:(NXImage *)newImage
  235. /*
  236.  * Called by the "reinit" methods to reset all of our instance
  237.  * variables based on using a new NXImage for our image.
  238.  */
  239. {
  240.     NXRect eBounds, neBounds;
  241.  
  242.     [image free];
  243.     image = newImage;
  244.     [self getExtendedBounds:&eBounds];
  245.     [image getSize:&neBounds.size];
  246.     neBounds.size.width *= bounds.size.width / originalSize.width;
  247.     neBounds.size.height *= bounds.size.height / originalSize.height;
  248.     neBounds.origin.x = bounds.origin.x - floor((neBounds.size.width - bounds.size.width) / 2.0 + 0.5);
  249.     neBounds.origin.y = bounds.origin.y - floor((neBounds.size.height - bounds.size.height) / 2.0 + 0.5);
  250.     [self setBounds:&neBounds];
  251.     [self getExtendedBounds:&neBounds];
  252.     NXUnionRect(&eBounds, &neBounds);
  253.     [image setDataRetained:YES];
  254.     [image getSize:&originalSize];
  255.     [image setScalable:YES];
  256.  
  257.     return neBounds;
  258. }
  259.  
  260. - (NXRect)reinitFromPasteboard:(Pasteboard *)pboard
  261. /*
  262.  * Reset all of our instance variable based on extract an
  263.  * NXImage from data in the the passed pboard.  Happens when
  264.  * we update a link through Object Links.
  265.  */
  266. {
  267.     NXRect neBounds;
  268.     NXImage *newImage;
  269.  
  270.     newImage = [NXImage allocFromZone:[self zone]];
  271.     if ((newImage = [newImage initFromPasteboard:pboard])) {
  272.     [newImage setDataRetained:YES];
  273.     if (checkImage(newImage)) return [self resetImage:newImage];
  274.     }
  275.  
  276.     [newImage free];
  277.     neBounds.origin.x = neBounds.origin.y = 0.0;
  278.     neBounds.size.width = neBounds.size.height = 0.0;
  279.  
  280.     return neBounds;
  281. }
  282.  
  283. - (NXRect)reinitFromFile:(const char *)file
  284. /*
  285.  * Reset all of our instance variable based on extract an
  286.  * NXImage from the data in the passed file.  Happens when
  287.  * we update a link through Object Links.
  288.  */
  289. {
  290.     NXRect neBounds;
  291.     NXImage *newImage;
  292.  
  293.     newImage = [[NXImage allocFromZone:[self zone]] init];
  294.     if ([newImage loadFromFile:file]) {
  295.     [newImage setDataRetained:YES];
  296.     if (checkImage(newImage)) return [self resetImage:newImage];
  297.     }
  298.  
  299.     [newImage free];
  300.     neBounds.origin.x = neBounds.origin.y = 0.0;
  301.     neBounds.size.width = neBounds.size.height = 0.0;
  302.  
  303.     return neBounds;
  304. }
  305.  
  306. /* All those allocation/initialization method and only this one free method. */
  307.  
  308. - free
  309. {
  310.     [image free];
  311.     return [super free];
  312. }
  313.  
  314. /* Link methods */
  315.  
  316. - setLink:(NXDataLink *)aLink
  317. /*
  318.  * It's "might" be linked because we're linked now, but might
  319.  * have our link broken in the future and the mightBeLinked flag
  320.  * is only advisory and is never cleared.  It is used just so that
  321.  * we know we might want to try to reestablish a link with this
  322.  * Graphic after a cut/paste.  No biggie if there really is no
  323.  * link associated with this any more.  In gvLinks.m, see
  324.  * readLinkForGraphic:fromPasteboard:useNewIdentifier:, and in
  325.  * gvPasteboard.m, see pasteFromPasteboard:andLink:at:.
  326.  * If this Image is a link button, then we obviously never need
  327.  * to update the link because we don't actually show the data
  328.  * associated with the link (we just show that little link button).
  329.  */
  330. {
  331.     NXDataLink *oldLink = link;
  332.     link = aLink;
  333.     gFlags.mightBeLinked = YES;
  334.     if (amLinkButton) [link setUpdateMode:NX_UpdateNever];
  335.     return oldLink;
  336. }
  337.  
  338. - (NXDataLink *)link
  339. {
  340.     return link;
  341. }
  342.  
  343. /* Event-handling */
  344.  
  345. - trackLinkButton:(NXEvent *)event at:(const NXPoint *)startPoint inView:(View *)view
  346. /*
  347.  * This method tracks that little link button.  Note that the link button is a diamond,
  348.  * but we track the whole rectangle.  This is unfortunate, but we can't be sure that,
  349.  * in the future, the shape of the link button might not change (thus, what we really
  350.  * need is a NeXTSTEP function to track the thing!).  Anyway, we track it and if the 
  351.  * mouse goes up inside the button, we openSource on the link (we wouldn't be here if
  352.  * we didn't have a link).
  353.  */
  354. {
  355.     NXPoint p;
  356.     NXImage *realImage, *highImage, *imageToDraw;
  357.  
  358.     p = *startPoint;
  359.     realImage = image;
  360.     highImage = [[self class] highlightedLinkButtonImage:&bounds.size];
  361.     image = imageToDraw = highImage;
  362.     [self draw];
  363.     [[view window] flushWindow];
  364.     do {
  365.     event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
  366.     p = event->location;
  367.     [view convertPoint:&p fromView:nil];
  368.     imageToDraw = NXMouseInRect(&p, &bounds, NO) ? highImage : realImage;
  369.     if (imageToDraw != image) {
  370.         image = imageToDraw;
  371.         [self draw];
  372.         [[view window] flushWindow];
  373.     }
  374.     } while (event->type != NX_MOUSEUP);
  375.  
  376.     if (imageToDraw == highImage) {
  377.     [link openSource];
  378.     image = realImage;
  379.     [self draw];
  380.     [[view window] flushWindow];
  381.     }
  382.  
  383.     return self;
  384. }
  385.  
  386. - (BOOL)handleEvent:(NXEvent *)event at:(const NXPoint *)p inView:(View *)view
  387. {
  388.     if (NXMouseInRect(p, &bounds, NO)) {
  389.     if (amLinkButton && !gFlags.selected && !(event->flags & (NX_CONTROLMASK|NX_SHIFTMASK|NX_ALTERNATEMASK))) {
  390.         [self trackLinkButton:event at:p inView:view];
  391.         return YES;
  392.     } else if (link && (event->data.mouse.click == 2) && (amIcon || (event->flags & NX_CONTROLMASK))) {
  393.         [NXApp getNextEvent:NX_MOUSEUPMASK];
  394.         [link openSource];
  395.         return YES;
  396.     }
  397.     }
  398.     return NO;
  399. }
  400.  
  401. /* Methods overridden from superclass to support links. */
  402.  
  403. - (int)cornerMask
  404. /*
  405.  * Link buttons are too small to have corners AND sides, so
  406.  * we only let link buttons have knobbies on the corners.
  407.  */
  408. {
  409.     if (amLinkButton) {
  410.     return LOWER_LEFT_MASK|UPPER_LEFT_MASK|UPPER_RIGHT_MASK|LOWER_RIGHT_MASK;
  411.     } else {
  412.     return [super cornerMask];
  413.     }
  414. }
  415.  
  416. - (NXRect *)getExtendedBounds:(NXRect *)theRect
  417. /*
  418.  * We have to augment this because we might have a link frame
  419.  * (if show links is on), so we have to extend our extended bounds
  420.  * a bit.
  421.  */
  422. {
  423.     NXRect linkBounds, *retval;
  424.     float linkFrameThickness = NXLinkFrameThickness();
  425.  
  426.     linkBounds = bounds;
  427.     linkBounds.origin.x -= linkFrameThickness;
  428.     linkBounds.size.width += linkFrameThickness * 2.0;
  429.     linkBounds.origin.y -= linkFrameThickness;
  430.     linkBounds.size.height += linkFrameThickness;
  431.  
  432.     retval = [super getExtendedBounds:theRect];
  433.  
  434.     return NXUnionRect(&linkBounds, retval);
  435. }
  436.  
  437. - (BOOL)constrainByDefault;
  438. /*
  439.  * Icons and link buttons look funny outside their natural
  440.  * aspect ratio, so we constrain them (by default) to keep
  441.  * their natural ratio.  You can still use the Alternate key
  442.  * to NOT constrain these.
  443.  */
  444. {
  445.     return (amLinkButton || amIcon);
  446. }
  447.  
  448. /* Methods overridden from superclass */
  449.  
  450. - (BOOL)isValid
  451. {
  452.     return image ? YES : NO;
  453. }
  454.  
  455. - (BOOL)isOpaque
  456. {
  457.     return [[image bestRepresentation] isOpaque];
  458. }
  459.  
  460. - (float)naturalAspectRatio
  461. {
  462.     if (!originalSize.height) return 0.0;
  463.     return originalSize.width / originalSize.height;
  464. }
  465.  
  466. - draw
  467. /*
  468.  * If we are resizing, we just draw a gray box.
  469.  * If not, then we simply see if our bounds have changed
  470.  * and update the NXImage object if they have.  Then,
  471.  * if we do not allow alpha (i.e. this is a TIFF image),
  472.  * we paint a white background square (we don't allow
  473.  * alpha in our TIFF images since it won't print and
  474.  * Draw is WYSIWYG).  Finally, we SOVER the image.
  475.  * If we are not keeping the cache around, we tell
  476.  * NXImage to toss its cached version of the image
  477.  * via the message recache.
  478.  *
  479.  * If we are linked to something and the user has chosen
  480.  * "Show Links", then linkOutlinesAreVisible, so we must
  481.  * draw a link border around ourself.
  482.  */
  483. {
  484.     NXRect r;
  485.     NXPoint p;
  486.     NXSize currentSize;
  487.  
  488.     if (bounds.size.width < 1.0 || bounds.size.height < 1.0) return self;
  489.  
  490.     if (DrawStatus == Resizing) {
  491.     PSsetgray(NX_DKGRAY);
  492.     PSsetlinewidth(0.0);
  493.     PSrectstroke(bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
  494.     } else if (image) {
  495.     p = bounds.origin;
  496.     [image getSize:¤tSize];
  497.     if (currentSize.width != bounds.size.width || currentSize.height != bounds.size.height) {
  498.         if ([image isScalable]) {
  499.         [image setSize:&bounds.size];
  500.         } else {
  501.         p.x = bounds.origin.x + floor((bounds.size.width - currentSize.width) / 2.0 + 0.5);
  502.         p.y = bounds.origin.y + floor((bounds.size.height - currentSize.height) / 2.0 + 0.5);
  503.         }
  504.     }
  505.     if ([[image bestRepresentation] isOpaque]) {
  506.         PSsetgray(NX_WHITE);
  507.         NXRectFill(&bounds);
  508.     }
  509.     [image composite:NX_SOVER toPoint:&p];
  510.     if (dontCache && NXDrawingStatus == NX_DRAWING) [image recache];
  511.     if ((NXDrawingStatus == NX_DRAWING) && !amLinkButton && [[link manager] areLinkOutlinesVisible]) {
  512.         r.origin.x = floor(bounds.origin.x);
  513.         r.origin.y = floor(bounds.origin.y);
  514.         r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
  515.         r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
  516.         NXFrameLinkRect(&r, YES);    // YES means "is a destination link"
  517.     }
  518.     }
  519.  
  520.     return self;
  521. }
  522.  
  523. /* Direct writing of EPS or TIFF. */
  524.  
  525. - (BOOL)canEmitEPS
  526. /*
  527.  * If we have a representation that can provide EPS directly, then,
  528.  * if we are copying PostScript to the Pasteboard and this Image is the
  529.  * only Graphic selected, then we might as well just have the EPS which
  530.  * represents this Image go straight to the Pasteboard rather than
  531.  * wrapping it up in the copyPSCodeInside: wrappers.  Of course, we
  532.  * can only do that if we haven't been resized.
  533.  *
  534.  * See gvPasteboard.m's writePSToStream:.
  535.  */
  536. {
  537.     List *reps = [image representationList];
  538.     int i = [reps count];
  539.  
  540.     if (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height) {
  541.     while (i--) {
  542.         if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
  543.         return YES;
  544.         }
  545.     }
  546.     }
  547.  
  548.     return NO;
  549. }
  550.  
  551. - writeEPSToStream:(NXStream *)stream
  552. /*
  553.  * If canEmitEPS above returns YES, then we can write ourself out directly
  554.  * as EPS.  This method does that.
  555.  */
  556. {
  557.     List *reps = [image representationList];
  558.     int i = [reps count];
  559.     char *data;
  560.     int length;
  561.  
  562.     while (i--) {
  563.     if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
  564.         [[reps objectAt:i] getEPS:&data length:&length];
  565.         NXWrite(stream, data, length);
  566.         return self;            // should I free data before returning?
  567.     }
  568.     }
  569.  
  570.     return self;
  571. }
  572.  
  573. - (BOOL)canEmitTIFF
  574. /*
  575.  * Similar to canEmitEPS, except its for TIFF.
  576.  */
  577. {
  578.     return (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height);
  579. }
  580.  
  581. - writeTIFFToStream:(NXStream *)stream
  582. /*
  583.  * Ditto above.
  584.  */
  585. {
  586.     [image writeTIFF:stream allRepresentations:YES];
  587.     return self;
  588. }
  589.  
  590. /* Caching. */
  591.  
  592. - setCacheable:(BOOL)flag
  593. {
  594.     dontCache = flag ? NO : YES;
  595.     return self;
  596. }
  597.  
  598. - (BOOL)isCacheable
  599. {
  600.     return !dontCache;
  601. }
  602.  
  603. /* Archiving. */
  604.  
  605. - write:(NXTypedStream *)stream
  606. /*
  607.  * All that is needed to archive the NXImage.
  608.  */
  609. {
  610.     [super write:stream];
  611.     NXWriteType(stream, "c", &amLinkButton);
  612.     NXWriteType(stream, "c", &amIcon);
  613.     if (!amLinkButton) {
  614.     NXWriteObject(stream, image);
  615.     NXWriteSize(stream, &originalSize);
  616.     }
  617.     return self;
  618. }
  619.  
  620. - read:(NXTypedStream *)stream
  621. /*
  622.  * This contains lots of compatibility code for
  623.  * interim versions.  See if you can figure out the
  624.  * various ways we approached archiving link info!
  625.  */
  626. {
  627.     BOOL alphaOk;
  628.     NXRect savedBounds;
  629.     int version, linkNumber;
  630.  
  631.     [super read:stream];
  632.     version = NXTypedStreamClassVersion(stream, "Image");
  633.     if (version > 5) NXReadType(stream, "c", &amLinkButton);
  634.     if (version > 6) NXReadType(stream, "c", &amIcon);
  635.     if (amLinkButton) {
  636.     savedBounds = bounds;
  637.     [self doInitFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]];
  638.     bounds = savedBounds;
  639.     } else {
  640.     image = NXReadObject(stream);
  641.     NXReadSize(stream, &originalSize);
  642.     }
  643.     if (version <= 2) NXReadTypes(stream, "c", &alphaOk);
  644.     if (version == 4) {
  645.     NXReadObject(stream);    // used to be the NXDataLink
  646.     } else if (version > 2 && version < 6) {
  647.     NXReadTypes(stream, "i", &linkNumber);
  648.     }
  649.  
  650.     return self;
  651. }
  652.  
  653. @end
  654.